﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime;
using System.Reflection;
using System.Globalization;
using System.Collections;
using System.IO;
using System.Diagnostics;

namespace ImPM.Application
{
    public static class ImPMTrace
    {
        // Fields
        private static bool _invalidFileOperationReported = false;
        [ThreadStatic]
        private static Guid _orgId = Guid.Empty;
        [ThreadStatic]
        private static Guid _userId = Guid.Empty;
        public const int BytesPerSizeUnit = 0x100000;
        public static Guid ClientContextOrganizationId = Guid.Empty;
        public const TraceBool DefaultCallStack = TraceBool.No;
        public const string DefaultTraceCategories = "*:Error";
        public const string DefaultTraceDirectory = @"c:\crmdrop\logs";
        public const int DefaultTraceFileSize = 0xa00000;
        public const string DefaultTraceFileSizeString = "10";
        public const int InitialFileSuffix = 1;
        private static string machineName;
        private static string processName;
        private static bool queryLocatorService = true;
        //private static TraceBuffer traceBuffer;
        private static bool traceBufferEnabled = false;
        private static SortedList traceCategoryList;
        [ThreadStatic]
        private static bool traceDisabled;
        private static SortedList traceLevelList;
        private static Hashtable traceLevelTable;

        // Methods
        static ImPMTrace()
        {
            LoadCategoriesToList();
                machineName = Environment.MachineName;
            traceDisabled = false;
        }

        internal static void Assert(bool condition, string message)
        {
            if (!condition)
            {
                throw new ImPMException(message, -2147220970, false);
            }
        }

          private static ImPMTraceSettings ChangeTraceSettings(FileChange fileChange, Guid organizationId, bool refresh)
        {
            ImPMTraceSettings traceSettings = ImPMTraceSettings.GetTraceSettings(organizationId);
            try
            {
                ImPMTraceSettings settings;
                    if (fileChange != FileChange.NewName)
                    {
                        if (fileChange != FileChange.NewSuffix)
                        {
                            if (fileChange != FileChange.KeepCurrent)
                            {
                                return ImPMTraceSettings.GetTraceSettings(organizationId);
                            }
                            settings = new ImPMTraceSettings(traceSettings.FileCountSuffix, organizationId);
                        }
                        else
                        {
                            settings = new ImPMTraceSettings(traceSettings.FileCountSuffix + 1, organizationId);
                        }
                    }
                    else
                    {
                        settings = new ImPMTraceSettings(1, organizationId);
                    }
               if (fileChange == FileChange.NewName)
                {
                    settings = ImPMTraceSettings.ChangeFileName(traceSettings, 1);
                }
                else if (fileChange == FileChange.NewSuffix)
                {
                    settings = ImPMTraceSettings.ChangeFileName(traceSettings, traceSettings.FileCountSuffix + 1);
                }
                else if (fileChange == FileChange.KeepCurrent)
                {
                    settings = ImPMTraceSettings.ChangeFileName(traceSettings, traceSettings.FileCountSuffix);
                }
                else
                {
                    return ImPMTraceSettings.GetTraceSettings(organizationId);
                }
                ImPMTraceSettings.SetTraceSetting(organizationId, settings);
            }
            catch (InvalidOperationException)
            {
                WriteEventError(0xc0004304L, new string[] { "Invalid Trace Directory" });
                return new ImPMTraceSettings();
            }
            catch (ThreadAbortException)
            {
                throw;
            }
            catch (Exception)
            {
                WriteEventError(0xc0004300L, new string[0]);
                return new ImPMTraceSettings();
            }
            return ImPMTraceSettings.GetTraceSettings(organizationId);
        }

        public static void ClearOrgId()
        {
            _orgId = Guid.Empty;
        }

        public static void ClearUserInfo()
        {
            _userId = Guid.Empty;
        }

        private static void DoWrite(TextWriter writer, string message)
        {
            Assert(ImPMTraceSettings.readerWriterLock.IsReaderLockHeld, "DoWrite must be called from within a Read Lock");
                if (writer != null)
                {
                    try
                    {
                        writer.WriteLine(message);
                    }
                    catch (Exception)
                    {
                    }
                }
        }

        private static StringBuilder FormatHeader(DateTime entryTime, ImPMTraceSettings localSettings)
        {
            string str, str2;
            bool traceDisabled = ImPMTrace.traceDisabled;
            StringBuilder builder = new StringBuilder();
            try
            {
                ImPMTrace.traceDisabled = true;
                str = Guid.Empty.ToString();
                str2 = Guid.Empty.ToString();
                
                builder.Append("# CRM Tracing Version 2.0");
                builder.Append(Environment.NewLine);
                builder.Append("# LocalTime: ");
                builder.Append(entryTime.ToString("yyyy-MM-dd HH:mm:ss.f", CultureInfo.InvariantCulture));
                builder.Append(Environment.NewLine);
                builder.Append("# CallStackOn: ");
                builder.Append(localSettings.TracingCallStack.ToString());
                builder.Append(Environment.NewLine);
                builder.Append("# ComputerName: ");
                builder.Append(machineName);
                builder.Append(Environment.NewLine);
                builder.Append("# CRMVersion: ");
                builder.Append(ProductVersion.FileVersion);
                builder.Append(Environment.NewLine);
          }
            catch (NullReferenceException exception)
            {
                builder.Append(exception.ToString());
            }
            finally
            {
                ImPMTrace.traceDisabled = traceDisabled;
            }
            return builder;
        }

        private static StringBuilder FormatTraceMessage(ImPMTraceSettings localSettings, TraceCategory traceCategory, TraceLevel traceLevel, Guid orgId, int skipFrames, DateTime entryTime, string format, object[] args)
        {
            StringBuilder output = new StringBuilder(200);
            CultureInfo currentCulture = null;
            try
            {
                currentCulture = Thread.CurrentThread.CurrentCulture;
                Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
                output.Append("[");
                output.Append(entryTime.ToString("yyyy-MM-dd HH:mm:ss.f", CultureInfo.InvariantCulture));
                output.Append("] ");
                output.Append("Process:");
                output.AppendFormat("{0,5}", processName);
                output.Append(" |Organization:");
                output.Append(orgId);
                output.Append(" |Thread:");
                output.AppendFormat("{0,5}", Thread.CurrentThread.ManagedThreadId);
                string name = Thread.CurrentThread.Name;
                if (name != null)
                {
                    output.Append("(");
                    output.Append(name.Replace(" ", "-"));
                    output.Append(")");
                }
                output.Append(" |Category: ");
                output.Append((string)traceCategoryList[traceCategory]);
                output.Append(" |User: ");
                output.Append(_userId);
                output.Append(" |Level: ");
                output.Append((string)traceLevelList[traceLevel]);
                output.Append(" | ");
                StackFrame frame = new StackFrame(skipFrames + 1, true);
                bool flag = false;
                if ((((localSettings.TracingCallStack == TraceBool.Yes) ? 1 : 0) != 0) || (traceCategory == TraceCategory.Exception))
                {
                    flag = true;
                }
                if (flag)
                {
                    StackFrameToStringBuilder(output, frame, false);
                    StackTrace trace = new StackTrace(skipFrames + 1, true);
                    output.Append(StackTraceToString(trace));
                }
                else
                {
                    StackFrameToStringBuilder(output, frame, false);
                    output.Append(Environment.NewLine);
                }
                output.Append(">");
                format = format.Replace(Environment.NewLine, Environment.NewLine + ">");
                if ((args == null) || (args.Length == 0))
                {
                    output.Append(format);
                    return output;
                }
                //output.AppendFormat(CrmTraceFormatProvider.Instance, format, args);
            }
            finally
            {
                if (currentCulture != null)
                {
                    Thread.CurrentThread.CurrentCulture = currentCulture;
                }
            }
            return output;
        }

        public static FileChange GetFileChange(string fileName, int traceFileSize, int fileCountSuffix, string traceDirectory)
        {
            FileChange keepCurrent=new FileChange();
            try
            {
                FileInfo info = new FileInfo(fileName);
                if (!info.Directory.Exists)
                {
                    return FileChange.Error;
                }
                if (info.Directory.Exists && IsCurrentFileNameDated(fileName, fileCountSuffix, traceDirectory))
                {
                    return FileChange.NewName;
                }
                if (info.Exists && (info.Length > traceFileSize))
                {
                    return FileChange.NewSuffix;
                }
                keepCurrent = FileChange.KeepCurrent;
            }
            catch (ArgumentException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
            catch (PathTooLongException)
            {
            }
            catch (NotSupportedException)
            {
            }
            return keepCurrent;
        }

        public static string GetFileName(bool returnPath, int fileCountSuffix, string traceDirectory)
        {
            string str = null;
            string str2 = null;
            string str3 = null;
            string str4 = null;
            DateTime now = DateTime.Now;
            if (traceDirectory == null)
            {
                return null;
            }
            str = AppDomain.CurrentDomain.BaseDirectory.Replace('/', '\\');
            if (str[str.Length - 1] == '\\')
            {
                str = str.Substring(0, str.Length - 1);
            }
            int num = str.LastIndexOf('\\');
            if (num >= 0)
            {
                str = str.Substring(num + 1);
            }
            str = str.Replace(':', '-');
            str3 = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
            str4 = string.Concat(new object[] { machineName, "-", ProcessName, "-", str, "-", str3, "-", fileCountSuffix, ".log" });
            str2 = Path.Combine(traceDirectory, str4);
            if (returnPath)
            {
                return str2;
            }
            return str4;
        }

        public static bool InsertAllCategoriesIntoHashtable(ArrayList rulePairs, Hashtable categoryHash)
        {
            foreach (string[] strArray in rulePairs)
            {
                if (!InsertAllCategoriesIntoHashtable(strArray, categoryHash))
                {
                    return false;
                }
            }
            return true;
        }

        public static bool InsertAllCategoriesIntoHashtable(string[] rulePair, Hashtable categoryHash)
        {
            TraceLevel off = TraceLevel.Off;
            if (traceLevelTable.Contains(rulePair[1]))
            {
                off = (TraceLevel)traceLevelTable[rulePair[1]];
            }
            else
            {
                WriteEventError(0xc0004302L, new string[] { rulePair[1] });
                return false;
            }
            foreach (TraceCategory category in traceCategoryList.Keys)
            {
                if (!categoryHash.Contains(category))
                {
                    categoryHash.Add(category, off);
                }
            }
            return true;
        }

        private static bool InsertCategoriesIntoHashtable(string[] rulePair, Hashtable categoryHash)
        {
            TraceCategory exception = TraceCategory.Exception;
            TraceLevel off = TraceLevel.Off;
            int index = 0;
            if (traceCategoryList.ContainsValue(rulePair[0]))
            {
                index = traceCategoryList.IndexOfValue(rulePair[0]);
                exception = (TraceCategory)traceCategoryList.GetKey(index);
            }
            else
            {
                WriteEventError(0xc0004303L, new string[] { rulePair[0] });
                return false;
            }
            if (traceLevelTable.Contains(rulePair[1]))
            {
                off = (TraceLevel)traceLevelTable[rulePair[1]];
            }
            else
            {
                WriteEventError(0xc0004302L, new string[] { rulePair[1] });
                return false;
            }
            categoryHash.Add(exception, off);
            return true;
        }

        public static bool InsertCategoriesIntoHashtable(ArrayList rulePairs, Hashtable categoryHash)
        {
            foreach (string[] strArray in rulePairs)
            {
                if (!InsertCategoriesIntoHashtable(strArray, categoryHash))
                {
                    return false;
                }
            }
            return true;
        }

        public static bool InsertSubcategoriesIntoHashtable(ArrayList rulePairs, Hashtable categoryHash)
        {
            foreach (string[] strArray in rulePairs)
            {
                if (!InsertSubCategoriesIntoHashtable(strArray, categoryHash))
                {
                    return false;
                }
            }
            return true;
        }

        private static bool InsertSubCategoriesIntoHashtable(string[] rulePair, Hashtable categoryHash)
        {
            TraceLevel off = TraceLevel.Off;
            ArrayList list = new ArrayList();
            if (traceLevelTable.Contains(rulePair[1]))
            {
                off = (TraceLevel)traceLevelTable[rulePair[1]];
            }
            else
            {
                WriteEventError(0xc0004302L, new string[] { rulePair[1] });
                return false;
            }
            rulePair[0] = rulePair[0].Substring(0, rulePair[0].Length - 2);
            int index = 0;
            foreach (string str in traceCategoryList.Values)
            {
                if (str.StartsWith(rulePair[0], StringComparison.OrdinalIgnoreCase))
                {
                    index = traceCategoryList.IndexOfValue(str);
                    list.Add((TraceCategory)traceCategoryList.GetKey(index));
                }
            }
            if (list.Count == 0)
            {
                WriteEventError(0xc0004303L, new string[] { rulePair[0] });
                return false;
            }
            foreach (TraceCategory category in list)
            {
                if (!categoryHash.Contains(category))
                {
                    categoryHash.Add(category, off);
                }
            }
            return true;
        }

        private static bool IsCurrentFileNameDated(string fileName, int fileCountSuffix, string traceDirectory)
        {
            return (fileName != GetFileName(true, fileCountSuffix, traceDirectory));
        }

        public static bool IsTracingOff(Guid orgId)
        {
            ImPMTraceSettings traceSettings = ImPMTraceSettings.GetTraceSettings(orgId);
            if (((traceSettings != null) && (traceSettings.IsTracingOff > TraceBool.Unset)) && (traceSettings.IsTracingOff != TraceBool.Yes))
            {
                return false;
            }
            return true;
        }

        private static void LoadCategoriesToList()
        {
            traceCategoryList = null;
            TraceCategory[] values = (TraceCategory[])Enum.GetValues(Type.GetType("Microsoft.Crm.TraceCategory"));
            traceCategoryList = new SortedList(values.Length);
            for (int i = 0; i < values.Length; i++)
            {
                traceCategoryList.Add(values[i], values[i].ToString().Replace("_", "."));
            }
            TraceLevel[] levelArray = (TraceLevel[])Enum.GetValues(typeof(TraceLevel));
            traceLevelTable = new Hashtable(levelArray.Length);
            for (int j = 0; j < levelArray.Length; j++)
            {
                traceLevelTable.Add(levelArray[j].ToString(), levelArray[j]);
            }
            traceLevelList = new SortedList(levelArray.Length);
            for (int k = 0; k < levelArray.Length; k++)
            {
                traceLevelList.Add(levelArray[k], levelArray[k].ToString());
            }
        }

        private static bool LoadTrace(Guid organizationId)
        {
            string str = string.Empty;
            try
            {
                str = "TraceBufferSize";
                str = "TraceBufferEnabled";
                ImPMTraceSettings setting = new ImPMTraceSettings(1, organizationId);
                ImPMTraceSettings.SetTraceSetting(organizationId, setting);
            }
            catch (NullReferenceException)
            {
                WriteEventError(0xc0004300L, new string[] { str });
                return false;
            }
            catch (InvalidCastException)
            {
                WriteEventError(0xc0004300L, new string[] { str });
                return false;
            }
            catch (InvalidOperationException)
            {
                if (!_invalidFileOperationReported)
                {
                    WriteEventError(0xc0004304L, new string[] { "Invalid Trace Directory" });
                    _invalidFileOperationReported = true;
                }
                return false;
            }
            catch (ThreadAbortException)
            {
                throw;
            }
            _invalidFileOperationReported = false;
            return true;
        }

        public static void LocatorServiceUpdated()
        {
            queryLocatorService = true;
        }

        public static void ResetTrace()
        {
            ResetTrace(null);
        }

        public static void ResetTrace(string traceDirectory)
        {
            ImPMTraceSettings.PurgeTraceSettings();
            LoadTrace(DefaultTraceSetting);
        }

        public static void SetOrgId(Guid orgId)
        {
            _orgId = orgId;
        }

        public static void SetUserInfo(Guid userId)
        {
            _userId = userId;
        }

        private static void StackFrameToStringBuilder(StringBuilder output, StackFrame frame, bool includeParameters)
        {
            MethodBase method = frame.GetMethod();
            if (method != null)
            {
                output.Append(method.ReflectedType.Name);
                output.Append(".");
                output.Append(method.Name);
                if (includeParameters)
                {
                    output.Append("(");
                    ParameterInfo[] parameters = method.GetParameters();
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        ParameterInfo info = parameters[i];
                        if (i > 0)
                        {
                            output.Append(", ");
                        }
                        output.Append(info.ParameterType.Name);
                        output.Append(" ");
                        output.Append(info.Name);
                    }
                    output.Append(")");
                }
            }
            string fileName = frame.GetFileName();
            if (fileName != null)
            {
                output.Append("  ");
                output.Append(fileName);
            }
            int fileLineNumber = frame.GetFileLineNumber();
            if (fileLineNumber > 0)
            {
                output.Append("(");
                output.Append(fileLineNumber.ToString(CultureInfo.InvariantCulture));
                output.Append(")");
            }
        }

        public static string StackTraceToString(StackTrace trace)
        {
            StringBuilder output = new StringBuilder(0x200);
            for (int i = 0; i <= (trace.FrameCount - 1); i++)
            {
                output.Append(Environment.NewLine + "\tat ");
                StackFrame frame = trace.GetFrame(i);
                StackFrameToStringBuilder(output, frame, true);
            }
            output.Append(Environment.NewLine);
            return output.ToString();
        }

        public static void Trace(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, object obj)
        {
            Write(orgId, traceCategory, traceLevel, 1, "{0}", new object[] { obj });
        }

        public static void Trace(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, string message)
        {
            Write(orgId, traceCategory, traceLevel, 1, message, null);
        }

        public static void Trace(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, int skipFrames, object obj)
        {
            Write(orgId, traceCategory, traceLevel, skipFrames + 1, "{0}", new object[] { obj });
        }

        public static void Trace(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, int skipFrames, string message)
        {
            Write(orgId, traceCategory, traceLevel, skipFrames + 1, message, null);
        }

        public static string TraceDirectory(Guid orgId)
        {
            return ImPMTraceSettings.GetTraceSettings(orgId).TraceDirectory;
        }

        public static void TraceFormat(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, string format, params object[] args)
        {
            Write(orgId, traceCategory, traceLevel, 1, format, args);
        }

        public static void TraceFormat(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, int skipFrames, string format, params object[] args)
        {
            Write(orgId, traceCategory, traceLevel, skipFrames + 1, format, args);
        }

        private static void Write(Guid orgId, TraceCategory traceCategory, TraceLevel traceLevel, int skipFrames, string format, params object[] args)
        {
            if (!ImPMTrace.traceDisabled)
            {
                bool traceDisabled = ImPMTrace.traceDisabled;
                try
                {
                    ImPMTrace.traceDisabled = true;
                    DateTime now = DateTime.Now;
                    ImPMTraceSettings localSettings = new ImPMTraceSettings();
                    if (RefreshTrace)
                    {
                        if (!LoadTrace(orgId))
                        {
                            localSettings = new ImPMTraceSettings();
                            localSettings.LoadState = LoadStatus.LoadFailed;
                        }
                        else
                        {
                            localSettings = ChangeTraceSettings(FileChange.KeepCurrent, orgId, true);
                            localSettings.LoadState = LoadStatus.LoadSuccessfulUnreported;
                        }
                    }
                    else
                    {
                        localSettings = ImPMTraceSettings.GetTraceSettings(orgId);
                    }
                    if (localSettings.IsTracingOff == TraceBool.Unset)
                    {
                        localSettings.IsTracingOff = TraceBool.Yes;
                    }
                    if ((((localSettings.LoadState == LoadStatus.LoadSuccessfulUnreported) || (localSettings.LoadState == LoadStatus.LoadSuccessfulReported)) && ((localSettings.IsTracingOff != TraceBool.Yes) || traceBufferEnabled)) && (traceLevel <= localSettings.GetTracingLevel(traceCategory)))
                    {
                        if (localSettings.IsTracingOff == TraceBool.No)
                        {
                            if (localSettings.FileName == null)
                            {
                                WriteEventError(0xc0004304L, new string[] { " Unable to Write file , Trace directory not defined" });
                                return;
                            }
                            FileChange fileChange = GetFileChange(localSettings.FileName, localSettings.TraceFileSize, localSettings.FileCountSuffix, localSettings.TraceDirectory);
                            switch (fileChange)
                            {
                                case FileChange.NewName:
                                case FileChange.NewSuffix:
                                    localSettings = ChangeTraceSettings(fileChange, orgId, false);
                                    break;
                            }
                            if ((localSettings.LoadState == LoadStatus.LoadSuccessfulUnreported) || (fileChange > FileChange.KeepCurrent))
                            {
                                StringBuilder builder = FormatHeader(now, localSettings);
                                using (new ImPMLock(ImPMTraceSettings.readerWriterLock, LockMode.Read))
                                {
                                    DoWrite(ImPMTraceSettings.Writer, builder.ToString());
                                }
                                localSettings.LoadState = LoadStatus.LoadSuccessfulReported;
                            }
                        }
                        if ((localSettings.IsTracingOff == TraceBool.No) || traceBufferEnabled)
                        {
                            StringBuilder message = FormatTraceMessage(localSettings, traceCategory, traceLevel, orgId, skipFrames + 1, now, format, args);
                            if (localSettings.IsTracingOff == TraceBool.No)
                            {
                                using (new ImPMLock(ImPMTraceSettings.readerWriterLock, LockMode.Read))
                                {
                                    DoWrite(ImPMTraceSettings.Writer, message.ToString());
                                }
                            }
                        }
                    }
                }
                finally
                {
                    ImPMTrace.traceDisabled = traceDisabled;
                }
            }
        }

        public static void WriteEventError(long eventId, params string[] startArguments)
        {
            WriteEventError("MSCRMTracing", eventId, startArguments);
        }

        public static void WriteEventError(string eventSource, long eventId, params string[] startArguments)
        {
            string[] strArray = new string[] { processName, AppDomain.CurrentDomain.BaseDirectory };
            string[] values = new string[startArguments.Length + strArray.Length];
            int index = 0;
            while (index < startArguments.Length)
            {
                values[index] = startArguments[index];
                index++;
            }
            while ((index - startArguments.Length) < strArray.Length)
            {
                values[index] = strArray[index - startArguments.Length];
                index++;
            }
                using (EventLog log = new EventLog())
                {
                    log.Source = eventSource;
                    using (ImPMEventLog log2 = new ImPMEventLog(log))
                    {
                        if (eventId == 0x4305L)
                        {
                            log2.WriteEntry(EventLogEntryType.Information, eventId, false, values);
                        }
                        else
                        {
                            log2.WriteEntry(EventLogEntryType.Error, eventId, false, values);
                        }
                    }
                }
        }

        // Properties
        public static Guid DefaultTraceSetting
        {
            get
            {
                return _orgId;
            }
        }

        public static string ProcessName
        {
            get
            {
                return processName;
            }
        }

        public static bool RefreshTrace
        {
            get
            {
                ImPMTraceSettings traceSettings;
                bool flag = false;
                    traceSettings = ImPMTraceSettings.GetTraceSettings(Guid.Empty, false);
                try
                {
                    traceDisabled = true;
                    if ((traceSettings != null) && (traceSettings.LoadState == LoadStatus.NotLoaded))
                    {
                        traceDisabled = false;
                        return true;
                    }
                    else
                    {
                        flag = false;
                    }
                    if (flag)
                    {
                        ImPMTraceSettings.PurgeTraceSettings();
                    }
                }
                catch (Exception)
                {
                    flag = false;
                }
                finally
                {
                    traceDisabled = false;
                }
                return flag;
            }
        }

        public static bool TraceDisabled
        {
            get
            {
                return traceDisabled;
            }
            set
            {
                traceDisabled = value;
            }
        }

        // Nested Types
        public enum FileChange
        {
            None,
            Error,
            KeepCurrent,
            NewSuffix,
            NewName
        }

        public enum LoadStatus
        {
            NotLoaded,
            LoadFailed,
            LoadSuccessfulUnreported,
            LoadSuccessfulReported
        }

        public enum TraceBool
        {
            Unset,
            Yes,
            No
        }
    }
}
